不少 C++ 的教學文都會拿 Lambda 表達式(Lambda expression)當範例,這是從 C++ 11 標準中開始出現的寫法,本篇就來快速看過什麼是 Lambda 表達式。
Lambda 表達式的語法如下:
[capture](parameters) -> return_type {
// function body
}
以下一一介紹各部位功能:
[capture]:捕獲,這是 Lambda 表達式最特別的地方,可以從同一作用域中抓取區域變數,而不必每個變數都透過參數抓進來,稍後將詳細說明。(parameters):參數,就跟一般函數的參數一樣。return_type { // function body }:這邊也跟一般函數一樣,但回傳的型別 return type 通常不寫。舉例來說:
[x](int y) -> int { return x + y; };
表示捕獲 x、拿到參數 y,函數本體是 x + y,函數回傳的型別是 int。
回傳型別通常由編譯器自行決定,因此可省略,寫成:
[x](int y) { return x + y; };
而假設該 Lambda 函數運算的結果要賦值於一變數,該變數型別通常寫成 auto,表示由編譯器自己判斷該變數型別,而非由我們手工指定,例如如果要賦值於變數 f,則寫成:
auto f = [x](int y) -> int { return x + y; };
// 或者
auto f = [x](int y) { return x + y; };
Lambda 表達式可以在同一作用域「捕獲(capture)」變數,表示不用把變數寫成參數,表達式也能抓到,例如:
#include <iostream>
using namespace std;
int main() {
int x = 10;
auto f = [x](int y) {
return x + y;
};
cout << "x = " << x << endl;
cout << "f(3) => x = 10, y = 3, result = " << f(3) << endl;
cout << "f(7) => x = 10, y = 7, result = " << f(7) << endl;
return 0;
}
這裡的 Lambda 表達式跟 x 在同一作用域 main() 中,因此 [x] 可直接捕獲 x,不用在呼叫 f 這個變數代表的 Lambda 表達式時,另外把 x 當參數傳進去。
因此在 f(3) 與 f(7) 中,雖然都只有吃參數 y(確切的值分別為 3 跟 7),但函數裡面一樣可以拿 x = 10 這個變數值去運算。
事實上,捕獲的方式有很多種,包含:
以下範例對照:
// 用值捕獲
int x = 10;
auto f = [=]() {
cout << x << endl; // 這裡的 x 是副本
};
x = 99; // 這裡重新賦值為 99 不影響 f 捕獲
f(); // 輸出依然為 10
// 用參考捕獲
int x = 10;
auto f = [&]() {
cout << x << endl; // 這裡的 x 是上面的 `int x`
};
x = 99; // 這裡重新賦值改到的 x 會影響 f 捕獲
f(); // 輸出為 99
兩者還可以混用,例如:
mutable 改副本在捕獲副本時,副本本身的值是不能在 Lambda 表達式修改的,例如:
int x = 10;
auto f = [x]() {
x = 20;
};
這樣會報錯,要改成:
int x = 10;
auto f = [x]() mutable {
x = 20; // 改變副本的值
cout << "x in f is " << x << endl;
};
f();
cout << "x out of f is " << x << endl;
這時 f 裡面的副本就可以被重新賦值為 20,但原本的變數 x 的值依然為 10,執行結果如下:
x in f is 20
x out of f is 10